package za.org.jcicada.genss.sensor.impl;

import java.util.Observable;

import org.apache.commons.logging.LogFactory;

import za.org.jcicada.genss.sensor.ISensor;
import za.org.jcicada.genss.sensor.ISensorType;
import za.org.jcicada.genss.sensor.ISensorValue;
import za.org.jcicada.genss.sensor.IStatusStrategy;
import za.org.jcicada.genss.sensor.SensorStatus;

/**
 * Concrete Sensor implementation extends Observable to implement the observer
 * pattern methods.
 * @author rcrida
 */
public class Sensor extends Observable implements ISensor {
    
    /** The sensor name which should be a dot separated string */
    private String name;
    
    /** A longer description of the sensor */
    private String description;
    
    /** The units that the sensor values take */
    private String units;
    
    /** Handle to the type object for the sensor which describes it */
    private ISensorType type;
    
    /** The current value of the sensor which has been tested to conform to the type */
    private ISensorValue value;
    
    /** The current status of the sensor with optional message and timestamp */
    private SensorStatusValue statusValue = new SensorStatusValue(SensorStatus.UNKNOWN, System.currentTimeMillis(), null);
    
    /**
     * The strategy used to calculate the sensor status based on its value
     */
    private IStatusStrategy statusStrategy = null;
    
    /**
     * Constructor
     * @param type the sensor type
     * @param name dot separated sensor name
     * @param description longer human readable sensor description
     * @param units the units of the sensor values
     */
    public Sensor(ISensorType type, String name, String description, String units) {
        this.type = type;
        this.name = name;
        this.description = description;
        this.units = units;
    }

    public ISensorType getType() {
        return type;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public String getUnits() {
        return units;
    }

    public ISensorValue getValue() {
        return value == null? new SensorValue(System.currentTimeMillis(), null): value;
    }
    
    public SensorStatusValue getStatusValue() {
    	return statusValue == null? new SensorStatusValue(SensorStatus.UNKNOWN): statusValue;
    }

	public void setValue(ISensorValue value) throws InvalidSensorValue {
        if (!type.isValid(value.getValue())) {
            String message = String.format("Invalid value (%s) for sensor of type %s", value.getValue(), type.toString());
            setStatusValue(new SensorStatusValue(SensorStatus.FAILURE, value.getTime(), message));
            LogFactory.getLog(Sensor.class).error(message);
            throw new InvalidSensorValue(message);
        } else {
            this.value = value;
            setChanged();
            notifyObservers(value);
            if (statusStrategy != null) {
            	setStatusValue(new SensorStatusValue(statusStrategy.getStatus(value.getValue()), value.getTime()));
            }
        }
    }
    
	public void setValue(long time, Object value) throws InvalidSensorValue {
		setValue(new SensorValue(time, value));
	}

	public void setValue(Object value) throws InvalidSensorValue {
		setValue(System.currentTimeMillis(), value);
	}

	public void setStatusValue(SensorStatusValue statusValue) {
		if (statusValue == null) {
			throw new IllegalArgumentException();
		}
		// only update if the status has changed
		if (statusValue.getStatus() != this.statusValue.getStatus() || (statusValue.getMessage() != null && !statusValue.getMessage().equals(this.statusValue.getMessage()))) {
			this.statusValue = statusValue;
			setChanged();
			notifyObservers(statusValue);
		}
	}

	public void setStatusStrategy(IStatusStrategy statusStrategy) {
		if (statusStrategy.isValidSensorType(type)) {
			this.statusStrategy = statusStrategy;
		} else {
			throw new IllegalArgumentException(String.format("Can't use %s with sensor type %s", statusStrategy.toString(), type.toString()));
		}
	}

	@Override
	public String toString() {
		return String.format("%s (%s), %s", name, units, type);
	}

	@Override
	public boolean equals(Object aOther) {
		if (this == aOther)
			return true;
		if (!(aOther instanceof Sensor))
			return false;
		Sensor other = (Sensor) aOther;
		if (!type.equals(other.type) || !name.equals(other.name)  || !description.equals(other.description) || !units.equals(other.units)) {
			return false;
		}
		if (!(value == null ? other.value == null : value.equals(other.value))) {
			return false;
		}
		if (!(statusValue == null ? other.statusValue == null : statusValue.equals(other.statusValue))) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		int hashCode = type.hashCode() + name.hashCode() + description.hashCode() + units.hashCode();
		if (value != null)
			hashCode += value.hashCode();
		if (statusValue != null)
			hashCode += statusValue.hashCode();
		return hashCode;
	}

}
